/*
 * File     : Parameter.java
 * Created  : 22 Apr 2011
 *
 * Copyright © 2011 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.dni.internal.call;

import static org.objectweb.asm.Opcodes.*;

import java.util.Map;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;

import com.googlecode.dni.internal.StructureFieldOrParameter;
import com.googlecode.dni.type.nativetype.NativeTypes;


/**
 * <p>
 *  Encapsulates a parameter to or a return value from a native call.
 * </p>
 * <p>
 *  A Java parameter may map to more than one native parameter; for example,
 *  structures.
 * </p>
 *
 * @author Matthew Wilson
 */
abstract class Parameter implements StructureFieldOrParameter
{

    private Class< ? > javaType;

    private char nativeType;

    private int javaIndex = -1;

    /**
     * Index of the Java variable for the method.  Note that 0 = this, and
     * that long and double each take two slots.
     */
    private int javaVarIndex = -1;

    private String libraryImplName;

    private int scratchMemorySizeVarIndex = -1;

    private Runnable scratchPointerWriter;


    /**
     * {@inheritDoc}
     */
    @Override
    public String toString()
    {
        String javaTypeName = this.javaType != null ? this.javaType.getName() : "<no Java type>";
        return "[" + getClass().getSimpleName() + " " + javaTypeName + " " + this.nativeType + " " + this.javaIndex + "]";
    }

    /**
     * @return <code>true</code> iff this parameter is an integer (of any size)
     *         or otherwise uses an integer slot
     */
    final boolean isInteger()
    {
        return "ZBSCIJ".indexOf( getNativeType() ) >= 0;
    }

    /**
     * @return the number of slots used by the Java parameter variable
     */
    abstract int getJavaVarSlots();

    /**
     * Allocates any fields necessary and puts them into the map.
     *
     * @param classWriter
     *            the class writer
     * @param createdFields
     *            the fields created so far; put any new fields in here
     */
    @SuppressWarnings( "unused" )
    void allocateFields( final ClassWriter classWriter,
                         final Map< String, Parameter > createdFields )
    {
        // nothing
    }

    /**
     * Writes a fragment for a static initialiser to set any static fields
     * required.
     *
     * @param methodVisitor
     *            the method visitor
     */
    @SuppressWarnings( "unused" )
    void writeStaticInitialiserFragment( final MethodVisitor methodVisitor )
    {
        // nothing
    }

    /**
     * Allocates any variable slots (initialising them if required) before any
     * other code is written in the method.
     *
     * @param methodVisitor
     *            the method visitor
     * @param varCount
     *            the number of variables in the method so far
     *
     * @return number of extra variables allocated
     */
    int allocatePrepareVarSlots( @SuppressWarnings( "unused" ) final MethodVisitor methodVisitor,
                                 @SuppressWarnings( "unused" ) final int varCount )
    {
        return 0;
    }

    /**
     * Determines if the preparation of this parameter requires scratch memory.
     *
     * @return <code>true</code> iff scratch memory is required
     */
    boolean needsScratchMemory()
    {
        return false;
    }

    /**
     * Writes byte-code to put the size of scratch memory required by this
     * parameter on the evaluation stack.
     *
     * @param methodVisitor
     *            the method visitor
     */
    // TODO v2.0 Can this be determined at compile-time as a constant?
    // Would that improve anything?
    void writeScratchMemorySize( @SuppressWarnings( "unused" ) final MethodVisitor methodVisitor )
    {
        throw new UnsupportedOperationException();
    }

    /**
     * Writes byte-code for preparing a parameter, before the parameters are
     * placed on the evaluation stack.
     *
     * @param methodVisitor
     *            the method visitor
     */
    @SuppressWarnings( "unused" )
    void writePrepareParameter( final MethodVisitor methodVisitor )
    {
        // nothing
    }

    /**
     * Writes byte-code to get the parameter in the correct form and place it on
     * the evaluation stack.
     *
     * @param methodVisitor
     *            the method visitor
     */
    abstract void writeGetParameter( MethodVisitor methodVisitor );

    /**
     * Writes byte-code for tidying a parameter, after the native call.  The
     * tidying may be called without the parameter having been fetched; the
     * code must cope with uninitialised state (e.g. nulls).
     *
     * @param methodVisitor
     *            the method visitor
     * @param isExceptionHandlerBlock
     *            <code>true</code> iff the code is in an exception handler
     *            block
     */
    @SuppressWarnings( "unused" )
    void writeTidyParameter( final MethodVisitor methodVisitor,
                             final boolean isExceptionHandlerBlock )
    {
        // nothing
    }

    /**
     * Determines if the parameter needs tidying after the native call.
     *
     * @return <code>true</code> iff tidying is required
     */
    boolean needsTidying()
    {
        return false;
    }

    /**
     * Notifies the parameter that the method is now complete.
     *
     * @param methodVisitor
     *            the method visitor
     * @param methodStart
     *            the label for the very start of the method
     */
    @SuppressWarnings( "unused" )
    void finishedMethod( final MethodVisitor methodVisitor,
                         final Label methodStart )
    {
        // nothing
    }

    /**
     * Writes byte-code to take the value from the evaluation stack and convert
     * it to the right form for the Java return type.
     *
     * @param methodVisitor
     *            the method visitor
     * @param returnVarIndex
     *            the var slot index of the return result
     */
    abstract void writeReturnParameter( MethodVisitor methodVisitor,
                                        int returnVarIndex );

    /**
     * @return the java type of the parameter (on the Java method)
     */
    @Override
    public final Class< ? > getJavaType()
    {
        return this.javaType;
    }

    /**
     * @param javaType
     *            the java type of the parameter (on the Java method)
     */
    void setJavaType( final Class< ? > javaType )
    {
        this.javaType = javaType;
    }

    /**
     * @return the native type for the native method (must be a primitive)
     */
    char getNativeType()
    {
        assert "ZBSCIJFDV".indexOf( this.nativeType ) >= 0 : this.nativeType + " in " + this;
        return this.nativeType;
    }

    /**
     * @param nativeType
     *            the native type for the native method (must be a primitive)
     */
    void setNativeType( final char nativeType )
    {
        assert "ZBSCIJFDV".indexOf( nativeType ) >= 0 : nativeType;
        this.nativeType = nativeType;
    }

    /**
     * Sets the native type to be that of a natively sized pointer.
     */
    void setNativeTypeAsPointer()
    {
        setNativeType( NativeTypes.POINTER_SIZE == 4 ? 'I' : 'J' );
    }

    /**
     * @return the index of the source Java parameter on the Java method
     *         (zero-based)
     */
    int getJavaIndex()
    {
        return this.javaIndex;
    }

    /**
     * @param javaIndex
     *            the index of the source Java parameter on the Java method
     *           (zero-based)
     */
    void setJavaIndex( final int javaIndex )
    {
        this.javaIndex = javaIndex;
    }

    /**
     * @return the index of the Java variable for the Java method; note that
     *         0 = this, and that long and double each take two slots
     */
    int getJavaVarIndex()
    {
        return this.javaVarIndex;
    }

    /**
     * @param javaVarIndex
     *             the index of the Java variable for the Java method; note that
     *             0 = this, and that long and double each take two slots
     */
    void setJavaVarIndex( final int javaVarIndex )
    {
        this.javaVarIndex = javaVarIndex;
    }

    /**
     * @return the library implementation type name
     */
    final String getLibraryImplName()
    {
        return this.libraryImplName;
    }

    /**
     * @param libraryImplName
     *            the library implementation type name
     */
    final void setLibraryImplName( final String libraryImplName )
    {
        this.libraryImplName = libraryImplName;
    }

    /**
     * @return the var index of the scratch size
     */
    final int getScratchMemorySizeVarIndex()
    {
        return this.scratchMemorySizeVarIndex;
    }

    /**
     * @param scratchMemorySizeVarIndex
     *            the slot index of the scratch size variable for this parameter
     * @param scratchPointerWriter
     *            a task that will write code to place the pointer to the
     *            parameter's scratch memory
     */
    void setScratchVariables( final int scratchMemorySizeVarIndex,
                              final Runnable scratchPointerWriter )
    {
        this.scratchMemorySizeVarIndex = scratchMemorySizeVarIndex;
        this.scratchPointerWriter = scratchPointerWriter;
    }


    //-- Internal Utility Methods ----------------------------------------------

    /**
     * Writes byte-code to place the return result on the evaluation stack.
     *
     * @param methodVisitor
     *            the method visitor
     * @param returnVarIndex
     *            the return var slot index
     */
    final void writeGetReturnResult( final MethodVisitor methodVisitor,
                                     final int returnVarIndex )
    {
        switch ( getNativeType() )
        {
            case 'V':
                break;

            case 'Z':
            case 'B':
            case 'S':
            case 'C':
            case 'I':
                methodVisitor.visitVarInsn( ILOAD, returnVarIndex );
                break;

            case 'J':
                methodVisitor.visitVarInsn( LLOAD, returnVarIndex );
                break;

            case 'F':
                methodVisitor.visitVarInsn( FLOAD, returnVarIndex );
                break;

            case 'D':
                methodVisitor.visitVarInsn( DLOAD, returnVarIndex );
                break;

            default:
                throw new AssertionError( getNativeType() );
        }
    }

    /**
     * Writes code to put the scratch pointer (when scratch is used) onto the
     * evaluation stack.  The type will be the size of a native pointer.
     */
    final void writeGetScratchPointer()
    {
        this.scratchPointerWriter.run();
    }

    /**
     * Writes code to put the scratch pointer (when scratch is used) onto the
     * evaluation stack.  The type will be a <code>long</code>.
     *
     * @param methodVisitor
     *            the method visitor
     */
    final void writeGetScratchPointer64( final MethodVisitor methodVisitor )
    {
        this.scratchPointerWriter.run();

        if ( NativeTypes.POINTER_SIZE == 4 )
        {
            methodVisitor.visitInsn( I2L );
            methodVisitor.visitLdcInsn( 0xffffffffL );
            methodVisitor.visitInsn( LAND );
        }
    }

}
